{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Chapter 6 - Evolutionary Algorithms\n",
    "### Deep Reinforcement Learning *in Action*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Listing 6.1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import random\n",
    "import numpy as np\n",
    "from matplotlib import pyplot as plt\n",
    "\n",
    "alphabet = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,.! \" #A\n",
    "target = \"Hello World!\" #B\n",
    "\n",
    "class Individual: #C\n",
    "    def __init__(self, string, fitness=0):\n",
    "        self.string = string\n",
    "        self.fitness = fitness\n",
    "\n",
    "from difflib import SequenceMatcher\n",
    "\n",
    "def similar(a, b): #D\n",
    "    return SequenceMatcher(None, a, b).ratio()\n",
    "\n",
    "def spawn_population(length=26,size=100): #E\n",
    "    pop = []\n",
    "    for i in range(size):\n",
    "        string = ''.join(random.choices(alphabet,k=length))\n",
    "        individual = Individual(string)\n",
    "        pop.append(individual)\n",
    "    return pop\n",
    "\n",
    "#A This is the list of characters we sample from to produce random strings\n",
    "#B This is the string we’re trying to evolve from a random population\n",
    "#C We set up a simple class to store information about each member of the population\n",
    "#D This method will compute a similarity metric between two strings, giving us a fitness score\n",
    "#E This method will produce an initial random population of strings"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Listing 6.2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "def recombine(p1_, p2_): #A\n",
    "    p1 = p1_.string\n",
    "    p2 = p2_.string\n",
    "    child1 = []\n",
    "    child2 = []\n",
    "    cross_pt = random.randint(0,len(p1))\n",
    "    child1.extend(p1[0:cross_pt])\n",
    "    child1.extend(p2[cross_pt:])\n",
    "    child2.extend(p2[0:cross_pt])\n",
    "    child2.extend(p1[cross_pt:])\n",
    "    c1 = Individual(''.join(child1))\n",
    "    c2 = Individual(''.join(child2))\n",
    "    return c1, c2\n",
    "\n",
    "def mutate(x, mut_rate=0.01): #B\n",
    "    new_x_ = []\n",
    "    for char in x.string:\n",
    "        if random.random() < mut_rate:\n",
    "            new_x_.extend(random.choices(alphabet,k=1))\n",
    "        else:\n",
    "            new_x_.append(char)\n",
    "    new_x = Individual(''.join(new_x_))\n",
    "    return new_x\n",
    "\n",
    "#A This function recombines two parent strings into two new offspring\n",
    "#B This function will mutate a string by randomly flipping characters"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Listing 6.3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 186,
   "metadata": {},
   "outputs": [],
   "source": [
    "def evaluate_population(pop, target): #A\n",
    "    avg_fit = 0\n",
    "    for i in range(len(pop)):\n",
    "        fit = similar(pop[i].string, target)\n",
    "        pop[i].fitness = fit\n",
    "        avg_fit += fit\n",
    "    avg_fit /= len(pop)\n",
    "    return pop, avg_fit\n",
    "\n",
    "def next_generation(pop, size=100, length=26, mut_rate=0.01): #B\n",
    "    new_pop = []\n",
    "    while len(new_pop) < size:\n",
    "        parents = random.choices(pop,k=2, weights=[x.fitness for x in pop])\n",
    "        offspring_ = recombine(parents[0],parents[1])\n",
    "        child1 = mutate(offspring_[0], mut_rate=mut_rate)\n",
    "        child2 = mutate(offspring_[1], mut_rate=mut_rate)\n",
    "        offspring = [child1, child2]\n",
    "        new_pop.extend(offspring)\n",
    "    return new_pop\n",
    "\n",
    "#A This function assigns a fitness score to each individual in the population\n",
    "#B This function generates a new generation by recombination and mutation"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Listing 6.4"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 203,
   "metadata": {},
   "outputs": [],
   "source": [
    "num_generations = 150\n",
    "population_size = 900\n",
    "str_len = len(target)\n",
    "mutation_rate = 0.00001 #A\n",
    "\n",
    "pop_fit = []\n",
    "pop = spawn_population(size=population_size, length=str_len) #B\n",
    "done = False\n",
    "for gen in range(num_generations):\n",
    "    pop, avg_fit = evaluate_population(pop, target)\n",
    "    pop_fit.append(avg_fit) #C\n",
    "    new_pop = next_generation(pop, \\\n",
    "        size=population_size, length=str_len, mut_rate=mutation_rate)\n",
    "    pop = new_pop\n",
    "    for x in pop: \n",
    "        if x.string == target: \n",
    "            print(\"Target Found!\")\n",
    "            done = True\n",
    "    if done:\n",
    "        break\n",
    "#A Set the mutation rate to 0.1%\n",
    "#B Create the initial random population\n",
    "#C Record population average fitness over training time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 204,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'QHelolo Tor!'"
      ]
     },
     "execution_count": 204,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pop.sort(key=lambda x: x.fitness, reverse=True) #sort in place, highest fitness first\n",
    "pop[0].string"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 207,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 864x504 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.figure(figsize=(12,7))\n",
    "plt.xlabel(\"Generations\",fontsize=22)\n",
    "plt.ylabel(\"Fitness\",fontsize=22)\n",
    "plt.plot(pop_fit)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Listing 6.5"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "15952"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "4*100 + 100 + 100*150 + 150 + 150*2 + 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 161,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "407"
      ]
     },
     "execution_count": 161,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "4*25 + 25 + 10*25 + 10 + 2*10 + 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 492,
   "metadata": {},
   "outputs": [],
   "source": [
    "def unpack_params(params, layers=[(25,4),(10,25),(2,10)]):\n",
    "    unpacked_params = []\n",
    "    e = 0\n",
    "    for i,l in enumerate(layers):\n",
    "        s,e = e,e+np.prod(l)\n",
    "        weights = params[s:e].view(l)\n",
    "        s,e = e,e+l[0]\n",
    "        bias = params[s:e]\n",
    "        unpacked_params.extend([weights,bias])\n",
    "    return unpacked_params\n",
    "\n",
    "def model(x,unpacked_params):\n",
    "    l1,b1,l2,b2,l3,b3 = unpacked_params\n",
    "    y = torch.nn.functional.linear(x,l1,b1)\n",
    "    y = torch.relu(y)\n",
    "    y = torch.nn.functional.linear(y,l2,b2)\n",
    "    y = torch.relu(y)\n",
    "    y = torch.nn.functional.linear(y,l3,b3)\n",
    "    y = torch.log_softmax(y,dim=0)\n",
    "    return y\n",
    "\n",
    "def spawn_population(N=50,size=407):\n",
    "    pop = []\n",
    "    for i in range(N):\n",
    "        vec = torch.randn(size) / 2.0\n",
    "        fit = 0\n",
    "        p = {'params':vec, 'fitness':fit}\n",
    "        pop.append(p)\n",
    "    return pop"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "#model(torch.randn(4).T,unpack_params(torch.randn(15952)/10))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Listing 6.6"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 491,
   "metadata": {},
   "outputs": [],
   "source": [
    "def recombine(x1,x2):\n",
    "    x1 = x1['params']\n",
    "    x2 = x2['params']\n",
    "    l = x1.shape[0]\n",
    "    split_pt = np.random.randint(l)\n",
    "    child1 = torch.zeros(l)\n",
    "    child2 = torch.zeros(l)\n",
    "    child1[0:split_pt] = x1[0:split_pt]\n",
    "    child1[split_pt:] = x2[split_pt:]\n",
    "    child2[0:split_pt] = x2[0:split_pt]\n",
    "    child2[split_pt:] = x1[split_pt:]\n",
    "    return {'params':child1, 'fitness':0.0}, {'params':child2, 'fitness': 0}\n",
    "\n",
    "def mutate(x, rate=0.01):\n",
    "    x_ = x['params']\n",
    "    num_to_change = int(rate * x_.shape[0])\n",
    "    idx = np.random.randint(low=0,high=x_.shape[0],size=(num_to_change,))\n",
    "    x_[idx] = torch.randn(num_to_change)/10.0\n",
    "    x['params'] = x_\n",
    "    return x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 814,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'fitness': 5.0,\n",
       " 'params': tensor([ 0.0000,  1.0000,  2.0000, -0.1315,  4.0000,  5.0000,  6.0000,  7.0000,\n",
       "          8.0000,  9.0000])}"
      ]
     },
     "execution_count": 814,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mutate({'fitness':5.0,'params':torch.arange(10).float()},rate=0.1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([ 0,  1,  2,  3,  4, -2, -2,  7,  8,  9])"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x = torch.arange(10)\n",
    "x[np.random.randint(0,10,(2,))] = -2\n",
    "x"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Listing 6.7"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 322,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torchvision as TV"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 332,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz\n",
      "Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz\n",
      "Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz\n",
      "Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz\n",
      "Processing...\n",
      "Done!\n"
     ]
    }
   ],
   "source": [
    "mnist_data = TV.datasets.MNIST(\"MNIST/\", train=True, download=False)\n",
    "mnist_test = TV.datasets.MNIST(\"MNIST/\", train=False, download=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 346,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([100, 10])"
      ]
     },
     "execution_count": 346,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x = mnist_data.train_data[0:100].float().flatten(start_dim=1)\n",
    "x /= x.max()\n",
    "params = torch.randn(100*784 + 100 + 50*100 + 50 + 50*10 + 10) / 10.0\n",
    "model(x,unpacked_params=unpack_params(params,layers=[(100,784),(50,100),(10,50)])).shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 347,
   "metadata": {},
   "outputs": [],
   "source": [
    "lossfn = torch.nn.NLLLoss()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "import gym\n",
    "\n",
    "env = gym.make(\"CartPole-v0\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 170,
   "metadata": {},
   "outputs": [],
   "source": [
    "def softmax(x,tau=1.2):\n",
    "    return torch.exp(x/tau) / torch.exp(x/tau).sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 844,
   "metadata": {},
   "outputs": [],
   "source": [
    "def test_model(agent,n=1):\n",
    "    scores = 0\n",
    "    n = 5\n",
    "    for i in range(n):\n",
    "        done = False\n",
    "        state = torch.from_numpy(env.reset()).float()\n",
    "        score = 0\n",
    "        while not done:\n",
    "            params = unpack_params(agent['params'])\n",
    "            probs = model(state,params)\n",
    "            action = torch.distributions.Categorical(probs=probs).sample()\n",
    "            state_, reward, done, info = env.step(action.item())\n",
    "            state = torch.from_numpy(state_).float()\n",
    "            score += 1\n",
    "        scores += score\n",
    "    scores /= n\n",
    "    return scores\n",
    "\n",
    "def test_model2(agent):\n",
    "    rids = np.random.randint(low=0,high=60000,size=(100,))\n",
    "    x = mnist_data.train_data[rids].float().flatten(start_dim=1)\n",
    "    x /= x.max()\n",
    "    #25*784 + 25 + 20*25 + 20 + 20*10 + 10\n",
    "    #params = torch.randn(100*784 + 100 + 50*100 + 50 + 50*10 + 10) / 10.0\n",
    "    p = unpack_params(agent['params'],layers=[(25,784),(20,25),(10,20)])\n",
    "    pred = model(x,unpacked_params=p)\n",
    "    y = mnist_data.train_labels[rids]\n",
    "    loss = lossfn(pred,y)\n",
    "    return loss\n",
    "\n",
    "def evaluate_population(pop):\n",
    "    tot_fit = 0\n",
    "    lp = len(pop)\n",
    "    scores = []\n",
    "    for agent in pop:\n",
    "        score = test_model(agent)\n",
    "        agent['fitness'] = score\n",
    "        tot_fit += score\n",
    "        scores.append(score)\n",
    "    scores = np.array(scores)\n",
    "    avg_fit = tot_fit / lp\n",
    "    return pop, avg_fit\n",
    "\n",
    "def next_generation2(pop,mut_rate=0.0,tau=20):\n",
    "    new_pop = []\n",
    "    while len(new_pop) < len(pop):\n",
    "        probs = 1.0 - softmax(torch.Tensor([x['fitness'] for x in pop]),tau=tau)\n",
    "        probs = softmax(probs,tau=tau).detach().numpy()\n",
    "        parents = np.random.choice(np.arange(len(pop)), size=2, replace=False, p = probs)\n",
    "        parent0,parent1 = pop[parents[0]],pop[parents[1]]\n",
    "        offspring_ = recombine(parent0,parent1)\n",
    "        child1 = mutate(offspring_[0], rate=mut_rate)\n",
    "        child2 = mutate(offspring_[1], rate=mut_rate)\n",
    "        offspring = [child1, child2]\n",
    "        new_pop.extend(offspring)\n",
    "    return new_pop\n",
    "\n",
    "def next_generation(pop,mut_rate=0.0,tau=20):\n",
    "    new_pop = []\n",
    "    lp = len(pop)\n",
    "    while len(new_pop) < len(pop):\n",
    "        rids = np.random.randint(low=0,high=lp,size=(int(0.2*lp)))\n",
    "        #batch_ = pop[rids]\n",
    "        batch = np.array([[i,x['fitness']] for (i,x) in enumerate(pop) if i in rids])\n",
    "        #scores = np.array([x['fitness'] for x in batch])\n",
    "        scores = batch[batch[:, 1].argsort()]\n",
    "        i0, i1 = int(scores[-1][0]),int(scores[-2][0])\n",
    "        parent0,parent1 = pop[i0],pop[i1]\n",
    "        offspring_ = recombine(parent0,parent1)\n",
    "        #child1,child2 = offspring_\n",
    "        child1 = mutate(offspring_[0], rate=mut_rate)\n",
    "        child2 = mutate(offspring_[1], rate=mut_rate)\n",
    "        offspring = [child1, child2]\n",
    "        new_pop.extend(offspring)\n",
    "    return new_pop"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 835,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "10.2"
      ]
     },
     "execution_count": 835,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "test_model(pop[0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 836,
   "metadata": {},
   "outputs": [],
   "source": [
    "#407, 20355\n",
    "pop = spawn_population(N=50,size=407)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 837,
   "metadata": {},
   "outputs": [],
   "source": [
    "#pop[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 838,
   "metadata": {},
   "outputs": [],
   "source": [
    "pop, avg_fit = evaluate_population(pop)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 839,
   "metadata": {},
   "outputs": [],
   "source": [
    "#pop[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 840,
   "metadata": {},
   "outputs": [],
   "source": [
    "pop = next_generation(pop,tau=2.0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 695,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([20355])"
      ]
     },
     "execution_count": 695,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pop[0]['params'].shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 540,
   "metadata": {},
   "outputs": [],
   "source": [
    "unpacked_params=unpack_params(pop[0]['params'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 541,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([-1.3802e+01, -1.9736e+01, -2.8192e+01, -2.2455e+01, -2.9587e+01,\n",
       "        -2.0290e+01, -5.1177e+01, -1.6032e-02, -2.5763e+01, -4.1412e+00])"
      ]
     },
     "execution_count": 541,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model(torch.randn(784),unpacked_params=unpack_params(pop[0]['params'],[(25,784),(20,25),(10,20)]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 542,
   "metadata": {},
   "outputs": [],
   "source": [
    "#np.array([x['fitness'] for x in pop])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 543,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([20355])"
      ]
     },
     "execution_count": 543,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pop[3]['params'].shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 544,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor(23.3850)"
      ]
     },
     "execution_count": 544,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "score = test_model(pop[3])\n",
    "score"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 571,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([24.924002 , 23.077421 , 18.767672 , 18.111404 , 20.924532 ,\n",
       "       20.819809 , 16.94826  , 15.128842 , 21.195002 , 19.789486 ,\n",
       "       16.615818 , 19.01187  , 15.509182 , 18.50777  , 20.348347 ,\n",
       "       23.037012 , 32.777363 , 14.465524 , 21.231054 , 16.594902 ,\n",
       "       25.661257 , 23.871231 , 21.998198 , 18.3474   , 14.749103 ,\n",
       "       21.832695 , 14.571891 , 17.703585 , 12.61941  , 20.1872   ,\n",
       "       16.195745 , 20.205597 , 20.414392 , 24.156153 , 16.840408 ,\n",
       "       21.297243 , 23.412085 , 24.184345 , 16.833782 , 22.892296 ,\n",
       "       23.54604  , 17.045483 , 20.732166 , 25.49101  , 14.3639345,\n",
       "       13.463415 , 25.544876 , 19.42682  , 24.488724 , 28.36776  ],\n",
       "      dtype=float32)"
      ]
     },
     "execution_count": 571,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.array([x['fitness'] for x in pop])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 548,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200,\n",
       "        0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200,\n",
       "        0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200,\n",
       "        0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200,\n",
       "        0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200,\n",
       "        0.0200, 0.0200, 0.0200, 0.0200, 0.0200])"
      ]
     },
     "execution_count": 548,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "softmax(torch.Tensor([x['fitness'] for x in pop]),tau=0.2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Listing 6.8"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 845,
   "metadata": {},
   "outputs": [
    {
     "ename": "KeyboardInterrupt",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mKeyboardInterrupt\u001b[0m                         Traceback (most recent call last)",
      "\u001b[0;32m<timed exec>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n",
      "\u001b[0;32m<ipython-input-844-7d9639349187>\u001b[0m in \u001b[0;36mevaluate_population\u001b[0;34m(pop)\u001b[0m\n\u001b[1;32m     34\u001b[0m     \u001b[0mscores\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     35\u001b[0m     \u001b[0;32mfor\u001b[0m \u001b[0magent\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mpop\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 36\u001b[0;31m         \u001b[0mscore\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtest_model\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0magent\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     37\u001b[0m         \u001b[0magent\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'fitness'\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mscore\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     38\u001b[0m         \u001b[0mtot_fit\u001b[0m \u001b[0;34m+=\u001b[0m \u001b[0mscore\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;32m<ipython-input-844-7d9639349187>\u001b[0m in \u001b[0;36mtest_model\u001b[0;34m(agent, n)\u001b[0m\n\u001b[1;32m      9\u001b[0m             \u001b[0mparams\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0munpack_params\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0magent\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'params'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     10\u001b[0m             \u001b[0mprobs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstate\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mparams\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 11\u001b[0;31m             \u001b[0maction\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtorch\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdistributions\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCategorical\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mprobs\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mprobs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msample\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     12\u001b[0m             \u001b[0mstate_\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mreward\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdone\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minfo\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0menv\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstep\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0maction\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mitem\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     13\u001b[0m             \u001b[0mstate\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtorch\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfrom_numpy\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstate_\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfloat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;32m~/anaconda3/envs/deeprl/lib/python3.6/site-packages/torch/distributions/categorical.py\u001b[0m in \u001b[0;36msample\u001b[0;34m(self, sample_shape)\u001b[0m\n\u001b[1;32m    106\u001b[0m         \u001b[0mprobs_2d\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mprobs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mreshape\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_num_events\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    107\u001b[0m         \u001b[0msample_2d\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtorch\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmultinomial\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mprobs_2d\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 108\u001b[0;31m         \u001b[0;32mreturn\u001b[0m \u001b[0msample_2d\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mreshape\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msample_shape\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    109\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    110\u001b[0m     \u001b[0;32mdef\u001b[0m \u001b[0mlog_prob\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31mKeyboardInterrupt\u001b[0m: "
     ]
    }
   ],
   "source": [
    "%%time\n",
    "num_generations = 25\n",
    "population_size = 500\n",
    "mutation_rate = 0.01 #A\n",
    "pop_fit = []\n",
    "pop = spawn_population(N=population_size,size=407) #B\n",
    "for i in range(num_generations):\n",
    "    pop, avg_fit = evaluate_population(pop)\n",
    "    pop_fit.append(avg_fit) #C\n",
    "    pop = next_generation(pop, mut_rate=mutation_rate,tau=5.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pop, avg_fit = evaluate_population(pop)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#pop[0:5]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 858,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 864x504 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.figure(figsize=(12,7))\n",
    "plt.xlabel(\"Generations\",fontsize=22)\n",
    "plt.ylabel(\"Score\",fontsize=22)\n",
    "plt.plot(running_mean(np.array(pop_fit),3))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 848,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "189.2"
      ]
     },
     "execution_count": 848,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "test_model(pop[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Listing 6.9"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Listing 6.10"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Listing 6.11"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Listing 6.12"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Listing 6.13"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Listing 6.14"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 204,
   "metadata": {},
   "outputs": [],
   "source": [
    "def running_mean(x,n=5):\n",
    "    conv = np.ones(n)\n",
    "    y = np.zeros(x.shape[0]-n)\n",
    "    for i in range(x.shape[0]-n):\n",
    "        y[i] = (conv @ x[i:i+n]) / n\n",
    "    return y"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python [conda env:deeprl]",
   "language": "python",
   "name": "conda-env-deeprl-py"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
